using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Win32;

namespace Dazzle
{
    /// <summary>
    /// Command lines understood:
    /// path.boxes         Open path.boxes for normal editing.
    /// /box -embedding    Start a COM server for the Box type.
    /// /boxes -embedding  Start a COM server for the BoxesDoc type.
    /// /reg               Re-add all registry settings.  Done automatically
    ///                    when running for the first time.
    /// </summary>
    static class Program
    {
        static System.Windows.Forms.Timer timer;
        public static bool startedForCom, isEmbedded;
        public static HiddenForm mainThread;
        //public static MainForm mainForm;
        public static bool shutdownInProgress = false;
        static int boxCount = 0;
        static ILog logger;
       

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            try {
                //LogManager.LogType = LogType.File;

                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

                logger = LogManager.GetLogger(typeof(Program));

                logger.Debug("Arg 1: " + (args.Length == 0 ? "(none)" : args[0]));
                
                // Set up registry entries, if required.
                int shift = 0;
                bool force_reg = args.Length > shift && args[shift].ToLower() == "/reg";
                if (force_reg)
                    shift++;
                SetUpRegistry(force_reg);

                // Were we started by COM?  /box arg is used when we are started
                // to provide the Box class, and /boxes when we are to provide
                // BoxesDoc.
                startedForCom = false;
                bool startedForBox = false;//, startedForBoxes = false;
                if (args.Length > shift) {
                    string arg = args[shift].ToLower();
                    if (arg == "/box") {
                        startedForCom = true;
                        startedForBox = true;
                    }
                    //if (arg == "/boxes") {
                    //    startedForCom = true;
                    //    startedForBoxes = true;
                    //}

                    logger.Debug(string.Format("startedForBox:{0}, startedForCom:{1}, arg={2}",
                        startedForBox, startedForCom, arg));

                    if (startedForCom) {
                        shift ++;
                        if (args.Length > shift) {
                            // Docs say COM will always add -embedding, but I have
                            // not observed this on Windows 2000.
                            arg = args[shift++].ToLower();
                            if (arg == "-embedding" || arg == "/embedding"
                                || arg == "-automation" || arg == "/automation")
                                shift++;
                        }
                    }

                    logger.Debug(string.Format("arg={0}", arg));
                }

                // Were we started to open a file?
                string load_path = args.Length > shift ? args[shift] : null;
                
                // Various bits of debugging code I might want again.
                //if (load_path != null) {
                //    int rc;

                //    IMoniker file, item, composite;
                //    rc = Win32.CreateFileMoniker(load_path, out file);
                //    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                //    rc = Win32.CreateItemMoniker("!", "asdasd", out item);
                //    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                //    rc = Win32.CreateGenericComposite(file, item, out composite);
                //    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                //    Debug.WriteLine(Win32.GetDisplayName(composite));
                //    Guid do_iid = Win32.GetGuidAttribute(typeof(System.Runtime.InteropServices.ComTypes.IDataObject));
                //    object o;
                //    composite.BindToObject(Win32.CreateBindCtx(), null, ref do_iid, out o);
                //    Debug.Assert(o is System.Runtime.InteropServices.ComTypes.IDataObject);

                //    //Guid class_id;
                //    //rc = Win32.GetClassFile(load_path, out class_id);
                //    //Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                //    //Debug.WriteLine(class_id);

                ////    Guid persist_file_iid = Win32.GetGuidAttribute(typeof(IPersistFile));
                ////    object o = Win32.CoCreateInstance(class_id, null, 4 /*CLSCTX_LOCAL_SERVER*/, persist_file_iid);
                ////    IPersistFile pf = (IPersistFile)o;
                ////    pf.Load(load_path, 0);

                ////    IParseDisplayName pdn = (IParseDisplayName)pf;
                ////    IBindCtx bc = Win32.CreateBindCtx();
                ////    int eaten;
                ////    IMoniker parsed_moniker;
                ////    rc = pdn.ParseDisplayName(bc, "Bozo the magnificent", out eaten, out parsed_moniker);
                ////    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));

                ////    IOleContainer oc = (IOleContainer)pf;
                ////    rc = oc.LockContainer(true);
                ////    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));

                ////    IOleItemContainer items = (IOleItemContainer)pf;
                ////    IntPtr item_o = IntPtr.Zero;
                ////    Guid iid = Win32.GetGuidAttribute(typeof(IOleObject));
                ////    rc = items.IsRunning("asdasd");
                ////    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                ////    rc = items.GetObject("asdasd", 2 /*INDEFINITE*/, bc, ref iid, ref item_o);
                ////    Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                ////    //Debug.Assert(item_o is IOleObject);

                //    //Guid guid = Win32.GetGuidAttribute(typeof(IOleItemContainer));
                //    //IntPtr guid_buf = Marshal.AllocCoTaskMem(Marshal.SizeOf(guid));
                //    //Marshal.StructureToPtr(guid, guid_buf, false);
                //    //Win32.MULTI_QI[] ifcs = new Win32.MULTI_QI[1];
                //    //ifcs[0] = new Win32.MULTI_QI();
                //    //ifcs[0].hr = Win32.S_OK;
                //    //ifcs[0].pIID = guid_buf;
                //    //ifcs[0].pItf = null;
                //    //rc = Win32.CoGetInstanceFromFile(IntPtr.Zero, ref class_id, null, 4 /*CLSCTX_LOCAL_SERVER*/,
                //    //                                 0, load_path, 1, ifcs);
                //    //Marshal.FreeCoTaskMem(guid_buf);
                //    //Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                //    //Debug.Assert(ifcs[0].pItf is IOleItemContainer);

                //    //IOleItemContainer word = (IOleItemContainer)ifcs[0].pItf;
                //    //IEnumUnknown ppenum;
                //    //word.EnumObjects(0, out ppenum)
                //    //ppenum.Next(1, , )

                ////    //Guid null_guid = new Guid();
                ////    //FORMATETC format_etc = new FORMATETC();
                ////    //format_etc.cfFormat = Win32.CF_ENHMETAFILE;
                ////    //format_etc.dwAspect = DVASPECT.DVASPECT_CONTENT;
                ////    //format_etc.lindex = -1;
                ////    //format_etc.ptd = IntPtr.Zero;
                ////    //format_etc.tymed = TYMED.TYMED_ENHMF;
                ////    //object o;
                ////    //// Fails with E_INVALIDARG.  Why?
                ////    //int rc = Win32.OleCreateFromFile(ref null_guid, load_path, ref thing_iid, 1 /*OLERENDER_DRAW*/,
                ////    //                        ref format_etc, null, null, out o);
                ////    //Debug.Assert(rc == Win32.S_OK, String.Format("{0:x}", rc));
                //}

                // We only want to server up either a single file or a single
                // Box so we only register whichever type library we know is
                // required.
                RegistrationServices reg = new RegistrationServices();
                int type_library_cookie = 0;
                if (startedForBox)
                    // Must have been started for an embedded Box.
                    type_library_cookie = reg.RegisterTypeForComClients(typeof(GraphBox), RegistrationClassContext.LocalServer, RegistrationConnectionType.SingleUse);
                //if (startedForBoxes)
                //    // Must have been started to serve a file.
                //    type_library_cookie = reg.RegisterTypeForComClients(typeof(BoxesDoc), RegistrationClassContext.LocalServer, RegistrationConnectionType.SingleUse);

                // Start our automatic garbage collection.
                timer = new System.Windows.Forms.Timer();
                timer.Interval = 100;
                timer.Tick += new EventHandler(timer_CollectGarbage);
                timer.Start();

                // For thread synchronisation.
                mainThread = new HiddenForm();

                // Main MDI form.
                //mainForm = new MainForm();

                if (startedForCom) {
                    // Leave main form invisible.
                    logger.Debug("Starting for COM in thread {0}", Thread.CurrentThread.ManagedThreadId);
                    Application.Run();
                } else {
                    //Log.LogClipboardContents();
                    //mainForm.OpenDocument(load_path);
                    //mainForm.Show();
                    Application.Run();
                }

                if (type_library_cookie != 0)
                    reg.UnregisterTypeForComClients(type_library_cookie);
            } catch (Exception e) {
                MessageBox.Show(e.ToString());
            }
        }

        static void log_FormClosed(object sender, FormClosedEventArgs e)
        {
            ExitIfNecessary();
        }

        static void timer_CollectGarbage(object sender, EventArgs e)
        {
            // If we do not do this then our COM-wrapped objects will never be
            // destroyed, which means our local server will not exit.
            // (Normally we are told when our COM objects are no longer
            // required, but this won't occur if the calling application is
            // unceremoniously killed.)
            GC.Collect();
        }

        public static void SetAsEmbedded(string container_app, string container_obj)
        {

            //if (mainForm != null)
            //    mainForm.SetAsEmbedded(container_app, container_obj);
            //Log.SetTitle(string.Format("{0} in {1}", container_app, container_obj));
            isEmbedded = true;
            logger.Info("isEmbedded = {0}, boxCount={1}", isEmbedded, boxCount);
        }

        public static void BoxCreated()
        {
            boxCount++;
        }

        public static void BoxDestroyed()
        {
            boxCount--;
            logger.Info("isEmbedded = {0}, boxCount={1}", isEmbedded, boxCount);
            if (isEmbedded && boxCount == 0) {
                //if (mainForm != null)
                //    mainForm.Dispose();
                //Log.ReadyToExit();
            }
            ExitIfNecessary();
        }

        // The program should not exit until:
        // If used for embedding:
        //    when the last Box is destroyed
        //    and Log is closed
        // Else:
        //    when mainForm is closed
        static void ExitIfNecessary()
        {
            //if (isEmbedded && boxCount == 0 && Log.form == null)
            // remove the condition' log.form == null' in order to exit.
            logger.Info("isEmbedded = {0}, boxCount={1}", isEmbedded, boxCount);
            if (isEmbedded && boxCount == 0) {
                Application.Exit();
            }
        }

        #region register com

        /// <summary>Creates a registry key if it does not exist, or if it does
        /// but /reg was provided on the command line.</summary>
        static bool CreateIfNeeded(string path, bool force, out RegistryKey key)
        {
            RegistryKey base_key = Registry.ClassesRoot;
            key = base_key.OpenSubKey(path);
            if (key != null) {
                if (!force)
                    // Assume the existing key is OK.  This helps avoid problems
                    // when we do not have write permission.
                    return false;
                key.Close();
                base_key.DeleteSubKeyTree(path);
            }
            key = base_key.CreateSubKey(path);
            return true;
        }

        /// <summary>Write our registry entries if they key is missing.  Leave 
        /// them alone if not to allow fiddling (and ease implementation!).
        /// </summary>
        static void SetUpRegistry(bool force)
        {
            string box_clsid = GraphBox.CLSID.ToString("B");
            //string boxes_clsid = BoxesDoc.ClsID.ToString("B");
            string version_independent_box_prog_id = "Gildata.Word.Graph.Box";
            string box_prog_id = version_independent_box_prog_id + ".1";
            //string version_independent_boxes_prog_id = "Gildata.Word.Graph.Picts";
            //string boxes_prog_id = version_independent_boxes_prog_id + ".1";
            string exe = Application.ExecutablePath;
            string typeLibGuid = "{2C91CC55-89AC-400B-AE42-87047873F8D4}";

            RegistryKey key;
            if (CreateIfNeeded("CLSID\\"+box_clsid, force, out key)) {
                key.SetValue(null, GraphBox.USER_TYPE);  // Human-readable name.

                RegistryKey aut = key.CreateSubKey("AuxUserType");
                aut.CreateSubKey("2").SetValue(null, GraphBox.USER_TYPE);  // Short name
                aut.CreateSubKey("3").SetValue(null, GraphBox.USER_TYPE_LONG);  // Full name

                // Do we ever need this?  We implement an enumerator, and the
                // contents depend on context (e.g. linking formats only included
                // when a moniker is available).
                //// n = <format,aspect,medium,direction>; aspect=-1 => all; order of
                //// preference.
                RegistryKey df = key.CreateSubKey("DataFormats");
                RegistryKey getSet = df.CreateSubKey("GetSet");
                getSet.CreateSubKey("0").SetValue(null, "Embed Source,1,8,1");
                getSet.CreateSubKey("1").SetValue(null, "3,1,32,1");   // CF_METAFILEPICT, DVASPECT_CONTENT, TYMED_MFPICT, DATADIR_GET
                getSet.CreateSubKey("2").SetValue(null, "2,1,16,1");   // CF_BITMAP, DVASPECT_CONTENT, TYMED_GDI, DATADIR_GET

                // Where to find an icon for this class.
                key.CreateSubKey("DefaultIcon").SetValue(null, exe+",0");

                // Use OLE's default in-process handler DLL.
                key.CreateSubKey("InprocHandler").SetValue(null, "ole2.dll");
                key.CreateSubKey("InprocHandler32").SetValue(null, "ole32.dll", RegistryValueKind.ExpandString);

                // What to run to instantiate a server.
                key.CreateSubKey("LocalServer32").SetValue(null, String.Format("\"{0}\" /box", exe));

                // Should be in the registry for when we are not running, but 
                // redundant with IOleObject.GetMiscStatus().
                // <aspect> = <status>
                //   1 = OLEMISC_RECOMPOSEONRESIZE
                //   2 = OLEMISC_ONLYICONIC
                //   4 = OLEMISC_INSERTNOTREPLACE
                //   8 = OLEMISC_STATIC
                //  16 = OLEMISC_CANTLINKINSIDE
                //  32 = OLEMISC_CANLINKBYOLE1
                //  64 = OLEMISC_ISLINKOBJECT
                // 128 = OLEMISC_INSIDEOUT            in-place activation
                // 256 = OLEMISC_ACTIVATEWHENVISIBLE  in-place activation
                // 512 = OLEMISC_RENDERINGISDEVICEINDEPENDENT
                RegistryKey ms = key.CreateSubKey("MiscStatus");
                ms.SetValue(null, "16");  // ???
                ms.CreateSubKey("1").SetValue(null, "17");  // DVASPECT_CONTENT

                key.CreateSubKey("ProgID").SetValue(null, box_prog_id);

                // <verb identifier>=<text>,<menu flags>,<verb flags>
                // <verb flags>: 1 = OLEVERBATTRIB_NEVERDIRTIES
                //               2 = OLEVERBATTRIB_ONCONTAINERMENU
                RegistryKey verb = key.CreateSubKey("verb");
                verb.CreateSubKey("-1").SetValue(null, "&Show,0,0");
                verb.CreateSubKey("-2").SetValue(null, "&Open,0,0");
                verb.CreateSubKey("-3").SetValue(null, "&Hide,0,1");
                verb.CreateSubKey("0").SetValue(null, "&Edit,0,2");
                verb.CreateSubKey("1").SetValue(null, "&Update,0,2");
                verb.CreateSubKey("2").SetValue(null, "&Property,0,2");
                key.CreateSubKey("TypeLib").SetValue(null, typeLibGuid);
                key.CreateSubKey("VersionIndependentProgID").SetValue(null, version_independent_box_prog_id);
            }
            //if (CreateIfNeeded("CLSID\\" + boxes_clsid, force, out key)) {
            //    key.SetValue(null, BoxesDoc.USER_TYPE+" (top reg)");  // Human-readable name.

            //    // Where to find an icon for this class.
            //    key.CreateSubKey("DefaultIcon").SetValue(null, exe + ",0");

            //    // What to run to instantiate a server.
            //    key.CreateSubKey("LocalServer32").SetValue(null, String.Format("\"{0}\" /boxes", exe));

            //    key.CreateSubKey("ProgID").SetValue(null, boxes_prog_id);

            //    key.CreateSubKey("VersionIndependentProgID").SetValue(null, version_independent_boxes_prog_id);
            //}

            // "ProgID" (programmatic identifier)
            if (CreateIfNeeded(box_prog_id, force, out key)) {
                key.SetValue(null, GraphBox.USER_TYPE);
                key.CreateSubKey("CLSID").SetValue(null, box_clsid);
                // Should appear in Insert Object dialog.
                key.CreateSubKey("Insertable");
            }
            //if (CreateIfNeeded(boxes_prog_id, force, out key)) {
            //    key.SetValue(null, BoxesDoc.USER_TYPE);
            //    key.CreateSubKey("CLSID").SetValue(null, boxes_clsid);
            //    // For when this file is double-clicked on in Explorer.
            //    key.CreateSubKey("Shell\\Open\\Command").SetValue(null, String.Format("\"{0}\" \"%1\"", exe));
            //}

            // VersionIndependentProgID
            if (CreateIfNeeded(version_independent_box_prog_id, force, out key)) {
                key.SetValue(null, GraphBox.USER_TYPE);
                key.CreateSubKey("CurVer").SetValue(null, box_prog_id);
            }

            string inerfaceGuid = Win32.GetGuidAttribute(typeof(IGraphBox)).ToString("B");
            
            //Registry.ClassesRoot.DeleteSubKey("Interface\\" + inerfaceGuid);

            if (CreateIfNeeded("Interface\\" + inerfaceGuid, force, out key)) {
                key.SetValue(null, "IGraphBox");

                key.CreateSubKey("ProxyStubClsid").SetValue(null, "{00020424-0000-0000-C000-000000000046}");
                key.CreateSubKey("ProxyStubClsid32").SetValue(null, "{00020424-0000-0000-C000-000000000046}");
                RegistryKey rgk = key.CreateSubKey("TypeLib");

                rgk.SetValue(null, typeLibGuid);
                rgk.SetValue("Version", "1.0", RegistryValueKind.String);
            }

            if (CreateIfNeeded("TypeLib\\" + typeLibGuid, force, out key)) {
                RegistryKey vk = key.CreateSubKey("1.0");
                vk.SetValue(null, "GraphBox 1.0 Type Library");


                RegistryKey zk = vk.CreateSubKey("0");
                zk.CreateSubKey("win32").SetValue(null, exe.Replace("exe", "tlb"), RegistryValueKind.String);

                vk.CreateSubKey("FLAGS").SetValue(null, 0, RegistryValueKind.String);
                vk.CreateSubKey("HELPDIR").SetValue(null, Path.GetDirectoryName(exe), RegistryValueKind.String);
            }
            //if (CreateIfNeeded(version_independent_boxes_prog_id, force, out key)) {
            //    key.SetValue(null, BoxesDoc.USER_TYPE);
            //    key.CreateSubKey("CurVer").SetValue(null, boxes_prog_id);
            //}

            //// Register our file extension.
            //if (CreateIfNeeded(".boxes", force, out key)) {
            //    key.SetValue(null, boxes_prog_id);
            //}
        }

        #endregion

        static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            //logger.Error("Parame Application_ThreadException", e);
        }
    }
}